// -----------------------------
// Author: 		Dhaenens Gert	
// Creation: 	10/9/2014 7:54:43 PM				
// -----------------------------

#include "RenderManager.h"

#include "Application.h"
#include "FileManager.h"
#include "Window.h"

#include "RenderPass.h"
#include "GeometryPass.h"
#include "ComputePass.h"
#include "ForwardPass.h"
#include "SkyboxPass.h"
#include "GUIPass.h"
#include "PostPass.h"

#include "Mutex.h"

#include <iomanip>

RenderManager::RenderManager()
	: m_lpDevice(nullptr)
	, m_lpOutputTarget(nullptr)
	, m_lpRenderTarget2D(nullptr)
	, m_lpDepthStencil(nullptr)
	, m_lpPositionTarget(nullptr)
	, m_lpNormalTarget(nullptr)
	, m_lpAlbedoTarget(nullptr)
	, m_lpAmbientTarget(nullptr)
	, m_lpRawRenderTarget3D(nullptr)
	, m_lpPostRenderTarget3D(nullptr)
	, m_RenderState(RenderState::Default)
	, m_lpDefaultRasterizer(nullptr)
	, m_lpDefaultNoCullRasterizer(nullptr)
	, m_lpWireframeRasterizer(nullptr)
	, m_lpMutex(nullptr)
	, m_ScreenshotIdx(0)
{
}

RenderManager::~RenderManager()
{
	for (RenderPass* lpPass : m_Passes)
		delete lpPass;

	delete m_lpMutex;

	NullRelease(&m_lpDefaultRasterizer);
	NullRelease(&m_lpDefaultNoCullRasterizer);
	NullRelease(&m_lpWireframeRasterizer);

	NullRelease(&m_lpPostRenderTarget3D);
	NullRelease(&m_lpRawRenderTarget3D);
	NullRelease(&m_lpAmbientTarget);
	NullRelease(&m_lpAlbedoTarget);
	NullRelease(&m_lpNormalTarget);
	NullRelease(&m_lpPositionTarget);

	NullRelease(&m_lpRenderTarget2D);
	NullRelease(&m_lpDepthStencil);
	NullRelease(&m_lpOutputTarget);
	NullRelease(&m_lpDevice);
}

Bool RenderManager::Init(Application* lpApp, Bool bVSync)
{
	m_bVSync = bVSync;

	// Get the Window Properties
	Window* lpWindow = lpApp->GetWindow();
	UInt32 width = lpWindow->GetWidth();
	UInt32 height = lpWindow->GetHeight();
	Bool isFullscreen = false;
	HWND hOutput = lpWindow->GetHandle();

	// Create the Render Device
	Bool isDebug = false;
	#ifdef _DEBUG
	isDebug = true;
	#endif
	Log::WriteLine(_T("[VIDEO]\t\tCreating Render Device (isDebug=%u)"), isDebug);
	Bool bUseD2D = true;
	#ifdef _NOD2D
	bUseD2D = false;
	#endif
	m_lpDevice = IRenderDevice::Create(hOutput, width, height, isFullscreen, isDebug, bUseD2D);
	if (!m_lpDevice)
		return false;

	// Output Information
	IAdapter* lpAdapter = m_lpDevice->GetAdapter();
	Log::WriteLine(_T("[VIDEO]\t\t***** ADAPTER INFO BEGIN *****"));
	Log::WriteLine(_T("[VIDEO]\t\t\tAdapter: %s"), lpAdapter->GetDescription());
	Log::WriteLine(_T("[VIDEO]\t\t\tDedicated Video Memory: %I64uMB (%I64u bytes)"), lpAdapter->GetDedicatedVideoMemory() / 1048576, lpAdapter->GetDedicatedVideoMemory());
	Log::WriteLine(_T("[VIDEO]\t\t\tDedicated System Memory: %I64uMB (%I64u bytes)"), lpAdapter->GetDedicatedSystemMemory() / 1048576, lpAdapter->GetDedicatedSystemMemory());
	Log::WriteLine(_T("[VIDEO]\t\t\tShared System Memory: %I64uMB (%I64u bytes)"), lpAdapter->GetSharedSystemMemory() / 1048576, lpAdapter->GetSharedSystemMemory());
	Log::WriteLine(_T("[VIDEO]\t\t***** ADAPTER INFO END *****"));

	// Create the Rasterizer States
	Log::WriteLine(_T("[VIDEO]\t\tCreating Default Rasterizer."));
	m_lpDefaultRasterizer = IRasterizerState::Create(m_lpDevice, CullMode::Back, FrontMode::Clockwise, FillMode::Solid);
	Log::WriteLine(_T("[VIDEO]\t\tCreating Default No Cull Rasterizer."));
	m_lpDefaultNoCullRasterizer = IRasterizerState::Create(m_lpDevice, CullMode::None, FrontMode::Clockwise, FillMode::Solid);
	Log::WriteLine(_T("[VIDEO]\t\tCreating Wireframe Rasterizer."));
	m_lpWireframeRasterizer = IRasterizerState::Create(m_lpDevice, CullMode::None, FrontMode::Clockwise, FillMode::Wireframe);

	// Set the Rasterizer State
	SetRenderState(RenderState::Default, false);

	// Create the Mutex
	Log::WriteLine(_T("[VIDEO]\t\tCreating Render Mutex."));
	m_lpMutex = new Mutex();
	if (!m_lpMutex->Init(_T("RenderManagerMutex")))
		return false;

	// If we are supposed to be Fullscreen
	// Switch before creating resources to avoid
	// duplicating resource creation
	if (lpWindow->IsFullscreen())
	{
		if (!m_lpDevice->SetFullscreenState(true))
			return false;
	}
	else
	{
		// Create Device Dependent Resources
		if (!CreateDependentResources(lpApp))
			return false;
	}

	// Create the Passes	
	RenderTargets targets = {};
	targets.OutputTarget = m_lpOutputTarget;
	targets.RawRenderTarget3D = m_lpRawRenderTarget3D;
	targets.PostRenderTarget3D = m_lpPostRenderTarget3D;
	targets.RenderTarget2D = m_lpRenderTarget2D;
	targets.DepthStencil = m_lpDepthStencil;
	targets.PositionTarget = m_lpPositionTarget;
	targets.NormalTarget = m_lpNormalTarget;
	targets.AlbedoTarget = m_lpAlbedoTarget;
	targets.AmbientTarget = m_lpAmbientTarget;

	Log::WriteLine(_T("[VIDEO]\t\tCreating Render Passes."));
	m_Passes.push_back(new SkyboxPass(this, targets));
	m_Passes.push_back(new GeometryPass(this, targets));
	m_Passes.push_back(new ComputePass(this, targets));
	m_Passes.push_back(new ForwardPass(this, targets));
	m_Passes.push_back(new PostPass(this, targets));
	#ifndef _NOD2D
	m_Passes.push_back(new GUIPass(this, targets));
	#endif

	// Initialize the Passes
	Log::WriteLine(_T("[VIDEO]\t\tInitializing Render Passes."));
	for (RenderPass* lpPass : m_Passes)
		lpPass->Init(lpApp);

	return true;
}
Bool RenderManager::CreateDependentResources(Application* lpApp)
{
	Window* lpWindow = lpApp->GetWindow();
	UInt32 width = lpWindow->GetWidth();
	UInt32 height = lpWindow->GetHeight();

	// Create the Depth Stencil
	Log::WriteLine(_T("[VIDEO]\t\tCreating Depth Stencil."));
	m_lpDepthStencil = IDepthStencil::Create(m_lpDevice, width, height);
	if (!m_lpDepthStencil)
		return false;

	// Create the Output Target
	Log::WriteLine(_T("[VIDEO]\t\tCreating Output Render Target."));
	m_lpOutputTarget = IRenderTarget::Create(m_lpDevice);
	if (!m_lpOutputTarget)
		return false;

	// Create the Raw Output Target 3D
	Log::WriteLine(_T("[VIDEO]\t\tCreating Raw Render Target (3D). (%ux%u)"), width, height);
	m_lpRawRenderTarget3D = IRenderTarget::Create(m_lpDevice, width, height, Format::Float4);
	if (!m_lpRawRenderTarget3D)
		return false;
	// Create the Post Output Target 3D
	Log::WriteLine(_T("[VIDEO]\t\tCreating Raw Render Target (3D). (%ux%u)"), width, height);
	m_lpPostRenderTarget3D = IRenderTarget::Create(m_lpDevice, width, height, Format::Float4);
	if (!m_lpPostRenderTarget3D)
		return false;
	
	#ifndef _NOD2D
	// Create the Output Target 2D
	Log::WriteLine(_T("[VIDEO]\t\tCreating Render Target (2D). (%ux%u)."), width, height);
	m_lpRenderTarget2D = IRenderTarget2D::Create(m_lpDevice, width, height);
	if (!m_lpRenderTarget2D)
		return false;
	#endif

	// Create the Targets
	Log::WriteLine(_T("[VIDEO]\t\tCreating Position Render Target. (%ux%u)"), width, height);
	m_lpPositionTarget = IRenderTarget::Create(m_lpDevice, width, height, Format::Float4);
	if (!m_lpPositionTarget)
		return false;
	Log::WriteLine(_T("[VIDEO]\t\tCreating Normal Render Target. (%ux%u)"), width, height);
	m_lpNormalTarget = IRenderTarget::Create(m_lpDevice, width, height, Format::Float4);
	if (!m_lpNormalTarget)
		return false;
	Log::WriteLine(_T("[VIDEO]\t\tCreating Albedo Render Target. (%ux%u)"), width, height);
	m_lpAlbedoTarget = IRenderTarget::Create(m_lpDevice, width, height, Format::Float4);
	if (!m_lpAlbedoTarget)
		return false;
	Log::WriteLine(_T("[VIDEO]\t\tCreating Ambient Render Target. (%ux%u)"), width, height);
	m_lpAmbientTarget = IRenderTarget::Create(m_lpDevice, width, height, Format::Float4);
	if (!m_lpAmbientTarget)
		return false;

	// Create the Viewport
	Log::WriteLine(_T("[VIDEO]\t\tSetting up Viewport (%u, %u, %u, %u)"), 0, 0, width, height);
	m_lpDevice->SetViewport(0.0f, 0.0f, (Float)width, (Float)height, 0.0f, 1.0f);

	// If the passes are already created, update them
	if (m_Passes.size() > 0)
	{
		RenderTargets targets = {};
		targets.OutputTarget = m_lpOutputTarget;
		targets.RawRenderTarget3D = m_lpRawRenderTarget3D;
		targets.PostRenderTarget3D = m_lpPostRenderTarget3D;
		targets.RenderTarget2D = m_lpRenderTarget2D;
		targets.DepthStencil = m_lpDepthStencil;
		targets.PositionTarget = m_lpPositionTarget;
		targets.NormalTarget = m_lpNormalTarget;
		targets.AlbedoTarget = m_lpAlbedoTarget;
		targets.AmbientTarget = m_lpAmbientTarget;

		for (RenderPass* lpPass : m_Passes)
			lpPass->UpdateTargets(lpApp, targets);
	}

	return true;
}
void RenderManager::Flush(Application* lpApp, Scene* lpScene)
{
	// Lock the Render Mutex
	m_lpMutex->TryEnter(-1);

	// Clear the Targets
	m_lpOutputTarget->Clear(0.5f, 0.5f, 0.5f, 1.0f);
	m_lpRawRenderTarget3D->Clear(0.0f, 0.0f, 0.0f, 1.0f);
	m_lpPostRenderTarget3D->Clear(0.0f, 0.0f, 0.0f, 1.0f);
	m_lpPositionTarget->Clear(0.0f, 0.0f, 0.0f, 1.0f);
	m_lpNormalTarget->Clear(0.5f, 0.5f, 1.0f, 1.0f);
	m_lpAlbedoTarget->Clear(0.0f, 0.0f, 0.0f, 1.0f);
	m_lpAmbientTarget->Clear(0.5f, 0.5f, 0.5f, 1.0f);
	m_lpDepthStencil->Clear(1.0f, 0x00);

	// Apply each pass
	for (RenderPass* lpPass : m_Passes)
	{
		if (lpPass->IsEnabled())
		{
			//TString passName = TString(lpPass->GetName().begin(), lpPass->GetName().end());
			//Log::WriteLine(_T("[RENDER]\tRendering Pass \"%s\""), passName.c_str());
			lpPass->Apply(lpApp, lpScene);
		}
	}

	// Present the Back Buffer
	m_lpDevice->Present(m_bVSync);

	// Unlock the Mutex
	m_lpMutex->Leave();
}
Bool RenderManager::Resize(Application* lpApp)
{
	Window* lpWindow = lpApp->GetWindow();
	UInt32 uNewWidth = lpWindow->GetWidth();
	UInt32 uNewHeight = lpWindow->GetHeight();

	// Make sure that the Device Exists
	if (!m_lpDevice)
		return true;

	Log::WriteLine(_T("[RENDER]\tResizing Render System (%ux%u)."), uNewWidth, uNewHeight);	

	// Clean the Resources First
	NullRelease(&m_lpDepthStencil);
	NullRelease(&m_lpOutputTarget);
	NullRelease(&m_lpRawRenderTarget3D);
	NullRelease(&m_lpPostRenderTarget3D);
	NullRelease(&m_lpRenderTarget2D);
	NullRelease(&m_lpPositionTarget);
	NullRelease(&m_lpNormalTarget);
	NullRelease(&m_lpAlbedoTarget);
	NullRelease(&m_lpAmbientTarget);

	// Resize the Device
	if (!m_lpDevice->Resize(uNewWidth, uNewHeight))
		return false;

	// Recreate Device Resources
	return CreateDependentResources(lpApp);
}

void RenderManager::SetRenderState(RenderState state, Bool bVerbose)
{
	m_RenderState = state;
	TString strState = _T("");

	IRasterizerState* lpState = nullptr;
	switch (state)
	{
	case RenderState::Default:
		lpState = m_lpDefaultRasterizer;
		strState = _T("Default");
		break;
	case RenderState::DefaultNoCull:
		lpState = m_lpDefaultNoCullRasterizer;
		strState = _T("Default No Cull");
		break;
	case RenderState::Wireframe:
		lpState = m_lpWireframeRasterizer;
		strState = _T("Wireframe");
		break;
	}
	if (bVerbose)
		Log::WriteLine(_T("[VIDEO]\t\tSetting Render State to \"%s\""), strState.c_str());
	m_lpDevice->SetRasterizerState(lpState);
}

IRenderDevice* RenderManager::GetDevice() const
{
	return m_lpDevice;
}

RenderState RenderManager::GetRenderState() const
{
	return m_RenderState;
}
RenderPass*	RenderManager::GetRenderPass(const AString& name) const
{
	auto foundIt = find_if(m_Passes.begin(), m_Passes.end(), [&name](RenderPass* lpPass)
	{
		if (lpPass->GetName() == name)
			return true;
		return false;
	});
	if (foundIt != m_Passes.end())
		return (*foundIt);
	return nullptr;
}

Mutex* RenderManager::GetMutex() const
{
	return m_lpMutex;
}

void RenderManager::SaveScreenshot()
{
	TStringStream fileStream;
	fileStream << _T("Screenshot") << std::setw(3) << std::setfill(_T('0')) << m_ScreenshotIdx << _T(".bmp");
	// Get the Texture of the Backbuffer
	m_lpOutputTarget->SaveTextureToFile(fileStream.str().c_str(), ImageFileFormat::BMP);
	++m_ScreenshotIdx;
}